package com.google.android.volley.elegant;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.conn.AbstractPoolEntry;
import org.apache.http.impl.conn.tsccm.AbstractConnPool;
import org.apache.http.impl.conn.tsccm.BasicPoolEntry;
import org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter;
import org.apache.http.impl.conn.tsccm.ConnPoolByRoute;
import org.apache.http.impl.conn.tsccm.PoolEntryRequest;
import org.apache.http.impl.conn.tsccm.RouteSpecificPool;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.conn.tsccm.WaitingThread;
import org.apache.http.impl.conn.tsccm.WaitingThreadAborter;
import org.apache.http.params.HttpParams;

public class ElegantThreadSafeConnManager extends ThreadSafeClientConnManager {

    public static class ElegantPool extends ConnPoolByRoute {

        protected BasicPoolEntry getEntryBlocking(HttpRoute route, Object state, long timeout, TimeUnit tunit,
            WaitingThreadAborter aborter) throws ConnectionPoolTimeoutException, InterruptedException {
            Date deadline=null;
            if(timeout > 0L) {
                deadline=new Date(System.currentTimeMillis() + tunit.toMillis(timeout));
            }

            BasicPoolEntry entry=null;
            poolLock.lock();
            try {

                RouteSpecificPool rospl=getRoutePool(route, true);
                WaitingThread waitingThread=null;

                while(entry == null) {

                    if(isShutDown) {
                        throw new IllegalStateException("Connection pool shut down");
                    }

                    // if (log.isDebugEnabled()) {
                    // log.debug("[" + route + "] total kept alive: " + freeConnections.size() +
                    // ", total issued: " + leasedConnections.size() +
                    // ", total allocated: " + numConnections + " out of " + maxTotalConnections);
                    // }

                    // the cases to check for:
                    // - have a free connection for that route
                    // - allowed to create a free connection for that route
                    // - can delete and replace a free connection for another route
                    // - need to wait for one of the things above to come true

                    entry=getFreeEntry(rospl, state);
                    if(entry != null) {
                        break;
                    }

                    boolean hasCapacity=rospl.getCapacity() > 0;

                    // if (log.isDebugEnabled()) {
                    // log.debug("Available capacity: " + rospl.getCapacity()
                    // + " out of " + rospl.getMaxEntries()
                    // + " [" + route + "][" + state + "]");
                    // }

                    if(hasCapacity && numConnections < maxTotalConnections) {

                        entry=createEntry(rospl, operator);

                    } else if(hasCapacity && !freeConnections.isEmpty()) {

                        deleteLeastUsedEntry();
                        // if least used entry's route was the same as rospl,
                        // rospl is now out of date : we preemptively refresh
                        rospl=getRoutePool(route, true);
                        entry=createEntry(rospl, operator);

                    } else {

                        // if (log.isDebugEnabled()) {
                        // log.debug("Need to wait for connection" +
                        // " [" + route + "][" + state + "]");
                        // }

                        if(waitingThread == null) {
                            waitingThread=newWaitingThread(poolLock.newCondition(), rospl);
                            aborter.setWaitingThread(waitingThread);
                        }

                        boolean success=false;
                        try {
                            rospl.queueThread(waitingThread);
                            waitingThreads.add(waitingThread);
                            success=waitingThread.await(deadline);

                        } finally {
                            // In case of 'success', we were woken up by the
                            // connection pool and should now have a connection
                            // waiting for us, or else we're shutting down.
                            // Just continue in the loop, both cases are checked.
                            rospl.removeThread(waitingThread);
                            waitingThreads.remove(waitingThread);
                        }

                        // check for spurious wakeup vs. timeout
                        if(!success && (deadline != null) && (deadline.getTime() <= System.currentTimeMillis())) {
                            throw new ConnectionPoolTimeoutException("Timeout waiting for connection");
                        }
                    }
                } // while no entry

            } finally {
                poolLock.unlock();
            }
            return entry;
        }

        public PoolEntryRequest requestPoolEntry(final HttpRoute httproute, final Object obj) {
            final WaitingThreadAborter aborter=new WaitingThreadAborter();
            return new PoolEntryRequest() {

                public void abortRequest() {
                    ElegantPool.this.poolLock.lock();
                    try {
                        aborter.abort();
                    } finally {
                        ElegantPool.this.poolLock.unlock();
                    }
                }

                public BasicPoolEntry getPoolEntry(long timeout, TimeUnit tunit) throws InterruptedException,
                    ConnectionPoolTimeoutException {
                    return getEntryBlocking(httproute, obj, timeout, tunit, aborter);
                }

            };
        }

        public ElegantPool(ClientConnectionOperator clientconnectionoperator, HttpParams httpparams) {
            super(clientconnectionoperator, httpparams);
        }
    }

    public static class ElegantBasicPooledConnAdapter extends BasicPooledConnAdapter {

        public final long startTime=System.currentTimeMillis();

        protected ElegantBasicPooledConnAdapter(ThreadSafeClientConnManager threadsafeclientconnmanager,
            AbstractPoolEntry abstractpoolentry) {
            super(threadsafeclientconnmanager, abstractpoolentry);
        }
    }

    public ElegantThreadSafeConnManager(HttpParams httpparams, SchemeRegistry schemeregistry) {
        super(httpparams, schemeregistry);
    }

    protected AbstractConnPool createConnectionPool(HttpParams httpparams) {
        ElegantPool elegantpool=new ElegantPool(connOperator, httpparams);
        if(true) {
            elegantpool.enableConnectionGC();
        }
        return elegantpool;
    }

    public void releaseConnection(ManagedClientConnection managedclientconnection, long l, TimeUnit timeunit) {
        if((managedclientconnection instanceof ElegantBasicPooledConnAdapter) && managedclientconnection.getRoute() != null) {
            ElegantBasicPooledConnAdapter elegantbasicpooledconnadapter=(ElegantBasicPooledConnAdapter)managedclientconnection;
            long l1=System.currentTimeMillis() - elegantbasicpooledconnadapter.startTime;
            long l2;
            if(managedclientconnection.getRoute().isSecure()) {
                l2=5000L;
            } else {
                l2=2500L;
            }
            if(l1 > l2) {
                try {
                    managedclientconnection.close();
                } catch(IOException ioexception) {
                }
            }
        }
        super.releaseConnection(managedclientconnection, l, timeunit);
    }

    public ClientConnectionRequest requestConnection(final HttpRoute httproute, final Object obj) {
        final PoolEntryRequest poolRequest=ElegantThreadSafeConnManager.this.connectionPool.requestPoolEntry(httproute, obj);
        return new ClientConnectionRequest() {

            public void abortRequest() {
                poolRequest.abortRequest();
            }

            public ManagedClientConnection getConnection(long l, TimeUnit timeunit) throws InterruptedException,
                ConnectionPoolTimeoutException {
                if(httproute == null) {
                    throw new IllegalArgumentException("Route may not be null.");
                } else {
                    BasicPoolEntry basicpoolentry=poolRequest.getPoolEntry(l, timeunit);
                    return new ElegantBasicPooledConnAdapter(ElegantThreadSafeConnManager.this, basicpoolentry);
                }
            }
        };
    }
}
